////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse
{
    using System;

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /// <summary>Provides generic implementations for the methods of <see cref="IVisitable{T}"/>.</summary>
    /// <threadsafety static="true" instance="false"/>
    public static class VisitableHelper
    {
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Lets <paramref name="visitor"/> visit <paramref name="visitable"/>.</summary>
        /// <exception cref="ArgumentNullException"><paramref name="visitor"/> and/or <paramref name="visitable"/> equal
        /// <c>null</c>.</exception>
        /// <typeparam name="TVisitableDerived">The type implementing <see cref="IVisitable{T, U}"/> interface or any of
        /// its subtypes, usually the most-derived type.</typeparam>
        /// <typeparam name="TVisitableBase">The visitable base type.</typeparam>
        /// <exception cref="InvalidCastException"><paramref name="visitable"/> cannot be cast to
        /// <typeparamref name="TVisitableDerived"/>. This can only happen when a type implementing
        /// <see cref="IVisitable{T, U}"/> does not pass its own or a derived type for the first parameter.</exception>
        /// <remarks>If <paramref name="visitor"/> can be cast to <see cref="IVisitor{T, U}"/> then calls
        /// ((<see cref="IVisitor{T, U}">IVisitor&lt;TVisitableDerived, TVisitableBase&gt;</see>)
        /// <paramref name="visitor"/>).<see cref="IVisitor{T, U}.OnVisit">OnVisit</see>(
        /// (<typeparamref name="TVisitableDerived"/>) <paramref name="visitable"/>); otherwise calls
        /// <paramref name="visitor"/>.<see cref="IVisitor{T}.OnVisit">OnVisit</see>(
        /// (<typeparamref name="TVisitableBase"/>) <paramref name="visitable"/>).</remarks>
        public static void Accept<TVisitableDerived, TVisitableBase>(
            IVisitable<TVisitableDerived, TVisitableBase> visitable, IVisitor<TVisitableBase> visitor)
            where TVisitableDerived : IVisitable<TVisitableDerived, TVisitableBase>
            where TVisitableBase : IVisitable<TVisitableBase>
        {
            if (visitable == null)
            {
                throw new ArgumentNullException("visitable");
            }

            AcceptImpl(visitable, visitor);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        internal static void AcceptImpl<TVisitableDerived, TVisitableBase>(
            IVisitable<TVisitableDerived, TVisitableBase> visitable, IVisitor<TVisitableBase> visitor)
            where TVisitableDerived : IVisitable<TVisitableDerived, TVisitableBase>
            where TVisitableBase : IVisitable<TVisitableBase>
        {
            AcceptImpl<TVisitableDerived, TVisitableBase>(
                visitable, visitor, visitor as IVisitor<TVisitableDerived, TVisitableBase>);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private static void AcceptImpl<TVisitableDerived, TVisitableBase>(
            IVisitable<TVisitableDerived, TVisitableBase> visitable,
            IVisitor<TVisitableBase> visitor,
            IVisitor<TVisitableDerived, TVisitableBase> genericVisitor)
            where TVisitableDerived : IVisitable<TVisitableDerived, TVisitableBase>
            where TVisitableBase : IVisitable<TVisitableBase>
        {
            if (genericVisitor == null)
            {
                if (visitor == null)
                {
                    throw new ArgumentNullException("visitor");
                }

                visitor.OnVisit((TVisitableBase)visitable);
            }
            else
            {
                genericVisitor.OnVisit((TVisitableDerived)visitable);
            }
        }
    }
}
